﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using PathFinder.Sources;

namespace PathFinder
{


    public class DrawManager
    {
        #region class definitions
        Graph graph;
        public Graph Graph { get { return graph; } set { graph = value; } }
        GraphPoint a;
        GraphPoint b;
        public Graphics graphics;
        public GraphLine selectedLine;
        Graphics tempGraphics;
        Bitmap tempBitmap;
        private Bitmap exportImage;
        public Bitmap ExportImage { get { return exportImage; } }
        ExtendedPanel drawScreen;

        public DrawManager(ExtendedPanel screen)
        {
            screen.drawablePicture.Paint += new System.Windows.Forms.PaintEventHandler(this.Redraw);
            drawScreen = screen;
            a = new GraphPoint(-1, -1);
            b = new GraphPoint(-1, -1);
            graphics = screen.drawablePicture.CreateGraphics();
            graph = new Graph(graphics);
            tempBitmap = new Bitmap(drawScreen.drawablePicture.Size.Width, drawScreen.drawablePicture.Size.Height);
            tempGraphics = Graphics.FromImage(tempBitmap);
            tempGraphics.DrawImage(drawScreen.background, new Rectangle(new Point(0, 0), drawScreen.background.Size));
        }
        #endregion

        #region creating line functions

        public void DrawCreatingLine(Point pt)
        {
            DrawingDispose();
            if (a.point.X != -1)
            {
                tempGraphics.DrawLine(GraphLine.RequiredPen, a.point, pt);
                a.Draw(tempGraphics);
            }
            GraphPoint dummy = new GraphPoint(pt.X, pt.Y);
            dummy.Draw(tempGraphics);
            RedrawStartEndPoints();
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void CutCreatingLine()
        {
            a = new GraphPoint(-1, -1);
            b = new GraphPoint(-1, -1);

            Redraw();
        }

        public void ClickOnBlank_DrawingMode(Point pt)
        {
            if (a.point.X == -1)
            {
                a = new GraphPoint(MainForm.instance.ValidPoint(pt));
            }
            else
            {
                if (a.IsNear(pt))
                    return;
                Point pt2 = MainForm.instance.ValidPoint(pt);
                b = new GraphPoint(pt2);
                graph.AddLine(a, b);
                a = b;
                b = null;
                Redraw();
            }
        }

        public void ClickOnExistingLine_DrawingMode(Point point, GraphLine line)
        {
            Point pt = MainForm.instance.ValidPoint(point);
            if (a.point.X == -1)
            {

                a = new GraphPoint(pt.X, pt.Y);
                graph.AddLine(line.a, a, line.properties);
                graph.AddLine(a, line.b, line.properties);
                line.a.wayCount--;
                line.b.wayCount--;
                graph.GraphLines.Remove(line);
                b = null;
                Redraw();
                return;
            }

            b = new GraphPoint(pt.X, pt.Y);
            graph.AddLine(line.a, b, line.properties);
            graph.AddLine(b, line.b, line.properties);
            graph.AddLine(a, b, line.properties);
            line.a.wayCount--;
            line.b.wayCount--;
            graph.GraphLines.Remove(line);
            a = b;
            b = null;
            Redraw();
            return;
        }

        public void ClickOnExistingPoint_DrawingMode(GraphPoint Pt)
        {
            if (a.point.X != -1)
                graph.AddLine(a, Pt);
            a = Pt;
        }

        #endregion

        #region supporting functions

        private void DrawingDispose()
        {
            graphics.Dispose();
            tempGraphics.Dispose();
            tempBitmap.Dispose();
            graphics = drawScreen.drawablePicture.CreateGraphics();
            tempBitmap = new Bitmap(drawScreen.drawablePicture.Size.Width, drawScreen.drawablePicture.Size.Height);
            tempGraphics = Graphics.FromImage(tempBitmap);
                tempGraphics.DrawImage(drawScreen.background, new Rectangle(new Point(0, 0), drawScreen.background.Size));
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                tempGraphics.SmoothingMode = SmoothingMode.HighQuality;
            for (int i = 0; i < graph.GraphLines.Count; i++)
            {
                    graph.GraphLines[i].DrawBackground(tempGraphics);
            }
            for (int i = 0; i < graph.GraphLines.Count; i++)
            {
                    graph.GraphLines[i].Draw(tempGraphics);
            }
            if (graph.start != null && graph.start == graph.end)
            {
                    graph.start.DrawStartEndSelected(tempGraphics);
            }
            else
                RedrawStartEndPoints();
        }

        private void Redraw(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            if (selectedLine == null)
            {
                Redraw();
            }
            else
            {
                RedrawWithSelectedLine(selectedLine);
            }
        }

        public void Redraw()
        {
            DrawingDispose();
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public static double DistanceBetweenPoints(Point a, Point b)
        {
            return Math.Pow((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y), 0.5);
        }
        public Point GetClosestPointToLine(Point p, ref GraphLine referenceLine)
        {
            referenceLine = null;
            double current = Double.MaxValue;
            double min = Double.MaxValue;
            Point retPt = new Point(-1, -1);
            foreach (GraphLine l in graph.GraphLines)
            {
                Point P = l.IsAtTheLine(p);
                if (P.X != -1)
                {
                    current = DistanceBetweenPoints(p, P);
                    if (current < min)
                    {
                        min = current;
                        referenceLine = l;
                        retPt = P;
                    }
                }
            }
            return retPt;
        }

        public void Clear()
        {
            a = new GraphPoint(-1, -1);
            b = new GraphPoint(-1, -1);
            graphics.Clear(Color.White);
            graph.Clear();
        }

        public bool IsReadyToCalculate()
        {
            return graph.start != null && graph.end != null;
        }

        #endregion

        #region deleting line functions

        public void RedrawDeletingLine(GraphLine line)
        {
            DrawingDispose();
            line.DrawDeleting(tempGraphics);
            RedrawStartEndPoints();
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        private void RedrawStartEndPoints()
        {
            if (graph.end != null && graph.end == graph.start)
            {
                graph.end.DrawStartEndSelected(tempGraphics);
            }
            else
            {
                if (graph.end != null)
                {
                    graph.end.DrawEndSelected(tempGraphics);
                }
                if (graph.start != null)
                {
                    graph.start.DrawStartSelected(tempGraphics);
                }
            }
        }

        public void RedrawDeletingPoint(GraphPoint point)
        {
            DrawingDispose();
            point.DrawDeleting(tempGraphics);
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void DeleteClick(GraphLine line)
        {
            graph.DeleteLine(line);
            Redraw();
        }

        public void DeletePoint(GraphPoint pt)
        {
            graph.DeletePoint(pt);
            Redraw();
        }

        #endregion

        #region redraw line functions

        public void RedrawMovingPoint(GraphPoint point)
        {
            DrawingDispose();
            if (point != graph.end && point != graph.start)
            {
                point.DrawMoving(tempGraphics);
            }
            else
            {
                if (point == graph.start && point == graph.end)
                {
                    point.DrawStartEndMouseOn(tempGraphics);
                }
                else if (point == graph.start)
                {
                    point.DrawStartMouseOn(tempGraphics);
                }
                else
                {
                    point.DrawEndMouseOn(tempGraphics);
                }
            }
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void RedrawStartMouseOnPoint(GraphPoint OnGraphPoint)
        {
            DrawingDispose();
            if (OnGraphPoint != graph.end)
            {
                OnGraphPoint.DrawStartMouseOn(tempGraphics);
            }
            else
            {
                OnGraphPoint.DrawStartEndMouseOn(tempGraphics);
            }
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void RedrawEndMouseOnPoint(GraphPoint OnGraphPoint)
        {
            DrawingDispose();
            if (OnGraphPoint != graph.start)
            {
                OnGraphPoint.DrawEndMouseOn(tempGraphics);
            }
            else
            {
                OnGraphPoint.DrawStartEndMouseOn(tempGraphics);
            }
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void RedrawPropertiesLine(GraphLine line)
        {
            DrawingDispose();
            line.DrawProperties(tempGraphics);
            RedrawStartEndPoints();
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        public void RedrawWithSelectedLine(GraphLine selected)
        {

            DrawingDispose();
            if (selected != null)
            {
                selected.DrawSelected(tempGraphics);
                RedrawStartEndPoints();
            }
            graphics.DrawImage(tempBitmap, new Point(0, 0));
        }

        #endregion

        #region Selectors

        public GraphPoint SelectPoint(Point p)
        {
            GraphPoint ptr = null;
            double dist = Double.MaxValue;
            double depository;
            foreach (GraphPoint point in graph.GraphPoints)
            {
                depository = point.GetDistance(p);
                if (dist > depository)
                {
                    ptr = point;
                    dist = depository;
                }
            }
            if (dist <= 12)
                return ptr;
            return new GraphPoint(-1, -1);
        }

        public GraphLine SelectLine(Point onLine)
        {
            return graph.GetLine(onLine);
        }

        public void SelectStartPoint(GraphPoint OnGraphPoint)
        {
            graph.start = OnGraphPoint;
        }

        public void SelectEndPoint(GraphPoint OnGraphPoint)
        {
            graph.end = OnGraphPoint;
        }

        #endregion

        #region merging functions

        public GraphPoint FindOverlappingPoint(GraphPoint selectedPoint)
        {
            foreach (GraphPoint pt in graph.GraphPoints)
            {
                if(pt != selectedPoint && pt.IsNear(selectedPoint.point))
                    return pt;
            }
            return new GraphPoint(-1,-1);
        }

        public void ClickOnExistingLine_MovingMode(GraphPoint onLine, GraphLine OnGraphLine)
        {
            graph.AddLine(OnGraphLine.a, onLine,OnGraphLine.properties);
            graph.AddLine(onLine, OnGraphLine.b,OnGraphLine.properties);
            OnGraphLine.a.wayCount--;
            OnGraphLine.b.wayCount--;
            graph.GraphLines.Remove(OnGraphLine);
            Redraw();
        }

        public Point FindOverlappingLine(Point point, ref GraphLine OnGraphLine, GraphPoint selectedPoint)
        {
            OnGraphLine = null;
            foreach (GraphLine l in graph.GraphLines)
            {
                Point P = l.IsAtTheLine(point);
                
                if (P.X != -1 && (l.a != selectedPoint && l.b != selectedPoint))
                {
                    OnGraphLine = l;
                    return P;
                }
            }
            return new Point(-1, -1);
        }

        public void MergePoints(GraphPoint point1, GraphPoint point2)
        {

            for (int i = 0; i < graph.GraphLines.Count; i++)
            {
                if ((graph.GraphLines[i].a.CompareTo(point1) && graph.GraphLines[i].b.CompareTo(point2) || (graph.GraphLines[i].a.CompareTo(point2) && graph.GraphLines[i].b.CompareTo(point1))))
                {
                    graph.GraphLines.RemoveAt(i);
                    point1.wayCount--;
                    point2.wayCount--;
                    break;
                }
            }
            for (int x = 0; x < graph.GraphLines.Count; x++)
            {
                if (graph.GraphLines[x].a == point2)
                {
                    graph.GraphLines[x].a = point1;
                }
                if (graph.GraphLines[x].b == point2)
                {
                    graph.GraphLines[x].b = point1;
                }
                for (int y = 0; y < graph.GraphLines.Count; y++)
                {
                    if (graph.GraphLines[x].HasSamePoints(graph.GraphLines[y]))
                    {
                        graph.GraphLines[x].a.wayCount--;
                        graph.GraphLines[x].b.wayCount--;
                        graph.GraphLines.RemoveAt(x);
                        x--;
                        break;
                    }
                }
            }
            point1.wayCount += point2.wayCount;
            graph.GraphPoints.Remove(point2);
            if (point1.wayCount == 0)
            {
                if (point2 == graph.start || point1 == graph.start)
                    graph.start = null;
                if (point2 == graph.end || point1 == graph.end)
                    graph.end = null;
                graph.GraphPoints.Remove(point1);
            }
            else
            {

                if (point2 == graph.start)
                    graph.start = point1; 
                if (point2 == graph.end)
                    graph.end = point1;
            }
        }

        #endregion

        public void DrawCompletePath()
        {
            List<GraphLine> euler = Graph.Output;
            List<GraphPoint> pts = Graph.GetGraphPointsFromGraphLineList(euler);
            int num = 1;
            if (pts.Count == 0)
            {
                pts.Add(Graph.start);
            }
            if (pts.Count == 1)
            {
                exportImage = null;
                return;
            }
               
            foreach (GraphPoint pt in pts)
            {
                if (pt.Enumerate(num))
                    num++;
            }
            graphics.Dispose();
            tempGraphics.Dispose();
            tempBitmap.Dispose();
            graphics = drawScreen.drawablePicture.CreateGraphics();
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            tempBitmap = new Bitmap(drawScreen.drawablePicture.Width, drawScreen.drawablePicture.Height);
            exportImage = new Bitmap(drawScreen.drawablePicture.Width, drawScreen.drawablePicture.Height);
            tempGraphics = Graphics.FromImage(tempBitmap);
            tempGraphics.SmoothingMode = SmoothingMode.HighQuality;
            tempGraphics.DrawImage(drawScreen.background, new Rectangle(new Point(0, 0), new Size(drawScreen.background.Width, drawScreen.background.Height)));


            for (int i = 0; i < euler.Count; i++)
            {
                euler[i].DrawBackground(tempGraphics);
            }

            for (int i = 0; i < euler.Count; i++)
            {
                euler[i].DrawExport(tempGraphics);
            }

            for (int i = 1; i < pts.Count - 1; i++)
            {
                    pts[i].DrawExportBackground(tempGraphics);
                pts[i].DrawWithNumber(tempGraphics, 1);
            }
            if(pts.Count > 0)
                if (pts[0] == pts[pts.Count - 1])
                {
                        pts[0].DrawExportBackground(tempGraphics);
                    pts[0].DrawWithNumber(tempGraphics, 3);
                }
                else
                {
                    pts[0].DrawExportBackground(tempGraphics);
                    pts[0].DrawWithNumber(tempGraphics, 0);
                    pts[pts.Count - 1].DrawExportBackground(tempGraphics);
                    pts[pts.Count - 1].DrawWithNumber(tempGraphics, 2);
                }
            Point topleft = new Point(), botright = new Point();
            GetPictureBounds(pts, ref topleft, ref botright);

            // graphics.DrawImage(tempBitmap, new Rectangle(topleft.X, topleft.Y, botright.X, botright.Y));
            graphics.DrawImage(tempBitmap, new Point());
            //exportImage = new Bitmap(drawScreen.drawablePicture.Size.Width + 200, drawScreen.drawablePicture.Size.Height);

            string Tmp = "";
            foreach (GraphPoint pt in pts)
                Tmp += " " + pt.Number;
            int wMin = 200, hMin = 100 + (1 + Tmp.Length/35)*15;
            if (botright.Y - topleft.Y + 16 > 200)
                hMin = botright.Y - topleft.Y + 16;

            if (botright.X - topleft.X + 200 + 16 > 100)
                wMin = botright.X - topleft.X + 200 + 16;

            exportImage = new Bitmap(wMin, hMin);
            Graphics tempGr = Graphics.FromImage(exportImage);
            tempGr.FillRectangle(new SolidBrush(Color.White), 0, 0, exportImage.Width, exportImage.Height);
            tempGr.DrawImage(tempBitmap, new Point(-topleft.X+8,-topleft.Y+8));
            tempGr.FillRectangle(new SolidBrush(Color.White), exportImage.Width - 198, 0, 198, exportImage.Height);
            SolidBrush blackCol = new SolidBrush(Color.Black);
            tempGr.DrawLine(new Pen(blackCol, 2), new Point(exportImage.Width - 198, 0), new Point(exportImage.Width - 198, exportImage.Height));
            Font drawFont = new System.Drawing.Font("Arial", 8);
            Font drawFontBig = new System.Drawing.Font("Arial", 12);
            Point origin = new Point(exportImage.Width -193, 10);
            String str = "";
            tempGr.DrawString("List of all traversed points", drawFontBig, blackCol, origin);
            origin.Y += 20;
            if(pts.Count == 1)
                tempGr.DrawString("None", drawFont, blackCol, origin);
            else
                foreach (GraphPoint pt in pts)
                {
                    if ((str + " " + pt.Number).Length >= 35)
                    {
                        tempGr.DrawString(str, drawFont, blackCol, origin);
                        origin.Y += 15;
                        str = "";
                    }

                    str += " " + pt.Number;
                }
            tempGr.DrawString(str, drawFont, blackCol, origin);
            origin.Y += 10;
            tempGr.Dispose();
            blackCol.Dispose();
            drawFont.Dispose();
            drawFontBig.Dispose();
        }

        private void GetPictureBounds(List<GraphPoint> pts, ref Point topleft, ref Point botright)
        {
            int minX = Int32.MaxValue;
            int minY = Int32.MaxValue;
            int maxX = 0;
            int maxY = 0;
            foreach (GraphPoint pt in pts)
            {
                if (pt.point.X < minX)
                    minX = pt.point.X;
                if (pt.point.X > maxX)
                    maxX = pt.point.X;
                if (pt.point.Y < minY)
                    minY = pt.point.Y;
                if (pt.point.Y > maxY)
                    maxY = pt.point.Y;
            }
            topleft = new Point(minX, minY);
            botright = new Point(maxX, maxY);
        }
    }
}
